<?php
/**
*
*
*/
abstract class Yz_Application_Abstract
    extends Yz_Class
    implements Yz_Application_Interface
{

    /**
    *   @var self::
    */
    protected static $instance;

    /**
    *   @var array
    */
    protected static $modules = array();

    /**
    *   @var array
    */
    protected static $namespaces;

    /**
     *  @var string
     */
    protected static $name;

    /**
    *   @var Yz_Config
    */
    protected $config;

    /**
     *  @var array
     */
    protected $configFiles = array(
        'config.php',
    );


    /******************************
    ***      Public methods     ***
    *******************************/


    /** The application's entry point
     *  @return int code: 0 = no error; code > 0: error code
     */
    public static function main()
    {
        defined( 'YZ_TIME' ) or define( 'YZ_TIME', microtime(true) );
        defined( 'YZ_ROOT' ) or define( 'YZ_ROOT', realpath('.') . '/' );
        $appClass = get_called_class();
        class_alias( $appClass, 'Yz' );
        Yz::$name = $appClass;

        Yz::$instance = $app = new Yz();
        return $app
            ->initLog()
            ->initErrorHandlers()
            ->initAssert()
            ->initConfig()
            ->initI18N()
            ->_main()
        ;
    }


    /** get the application-wide configuration object
     *  NOTE If this method gets called before ::main() the $instance will not be available
     *  @return Yz_Config
     */
    public static function getConfig()
    {
        return Yz::$instance->config;
    }


    /** fetch array of InstalledNamespaces from config
     *  @return array
     */
    public static function getInstalledNamespaces()
    {
        Log::log(null, Log::DEBUG4);
        $n =& Yz::$namespaces;
        if (null === $n) {
            $app = Yz::$name;
            $n = array($app);
            if ('Yz' !== $app) {
                $n[] = 'Yz';
            }
        }
        return $n;
    }


    /** get the application's name
     *  @return string
     */
    public static function getName()
    {
        Log::log(null, Log::DEBUG4);
        return Yz::$name;
    }

    /** get the application's main namespace
     *  @return string
     */
    public static function getAppNamespace()
    {
        Log::log(null, Log::DEBUG4);
        return Yz::$name;
    }

    /** load module by its short name
    *   NOTE If this method gets called before ::main() the $instance will not be available
    *   @param string selector
    *   @return module object
    *   @throw Yz_Exception_ModuleNotFound
    *   @throw Yz_Exception_InvalidArgument
    *   @throw Yz_Exception_InvalidValueType
    */
    public static function selectModule( $moduleName )
    {
        Log::log(null, Log::DEBUG);

        $modules =& Yz::$modules;

        $moduleName = ucfirst(Yz_String::testIdentifier($moduleName, 'module name'));
        if (array_key_exists($moduleName, $modules)) {
            return $modules[$moduleName];
        }

        foreach (Yz::getInstalledNamespaces() as $ns) {
            $moduleClass = sprintf("%s\\modules\\%s\\Module",
                ucfirst($ns),
                $moduleName
            );
            $class = Yz::loadClass($moduleClass);
            if (false !== $class) {
                $module = $class::getInstance();
                $modules[$moduleName] = $module;
                return $module;
            }
        }
        throw Yz_Exception_ModuleNotFound::_new( array("moduleName" => $moduleName) );
    }

    /**
     *  User-API method to load a class or verify that it's loaded
    *   @param string className
    *   @return className|false
    */
    public static function loadClass( $className )
    {
        return Autoloader::loadClass($className);
    }

    /**
     *
     */
    public function __destruct()
    {
        Log::log( sprintf("Included files: %d, classes: %d, interfaces: %d",
                count(get_included_files()),
                count(get_declared_classes()),
                count(get_declared_interfaces())
            ),
            Log::DEBUG
        );
        Log::log("Used memory: ".memory_get_usage(), Log::DEBUG);
        Log::log("END_LOG", Log::INFO);
    }


    /******************************
    ***     Protected methods   ***
    *******************************/

    /**
     *  final protected constructor: prevent any code execution within it
     */
    final protected function __construct() { }


    /** the main function of the application
     *  @return int
     */
    protected function _main()
    {
        return 0;
    }

    /**
    *   @return (ignored)
    */
    protected function displayErrorMessage( $message )
    {
        $class = get_class($this);
        file_put_contents( 'php://stderr', "[Yz] $class: ERROR: $message.\n" );
        return $this;
    }


    /**
     *
     */
    protected function initConfig()
    {
        $myconfig = new Yz_Config( null, 'App.Config' );
        foreach ($this->configFiles as $cf) {
            $file = new Yz_File($cf, YZ_LIB);
            if (! $file->isFile()) {
                continue;
            }
            Log::log("Include config file '{$cf}'", Log::DEBUG);
            $configString = include( "$cf" );
            if ( "" !== "$configString" ) {
                $myconfig = Yz_Config_Loader::load( $configString, 'App.Config', $myconfig );
            }
            Log::log("Config '{$myconfig}'", Log::DEBUG4);
        }
        $this->config = $myconfig;
        return $this;
    }

    /** setup the php `error' and exception handlers
     *  @return this
     */
    protected function initErrorHandlers()
    {
        // force all php 'errors' to be handled
        error_reporting( -1 );

        // set the error handler
        set_error_handler( array( $this, 'errorHandler' ) );

        // set the exception handler
        set_exception_handler( array( $this, 'exceptionHandler' ) );
        return $this;
    }

    /** setup the assertion handler
     *  @return this
     */
    protected function initAssert()
    {
        // Activate assert w/o warnings and make it quiet
        assert_options(ASSERT_ACTIVE, 1);
        assert_options(ASSERT_WARNING, 0);
        assert_options(ASSERT_QUIET_EVAL, 1);
        // set the exception handler
        assert_options(ASSERT_CALLBACK, array( $this, 'assertHandler' ) );
        return $this;
    }

    /** init log and add a writer for ERROR level into STDERR
     *  @return this
     */
    protected function initLog()
    {
        class_alias( 'Yz_Log_Log', 'Log' );
        Log::addWriter(
            new Yz_Log_Writer_StdErr(),
            Log::ERROR
        );
        return $this;
    }


    /** load the i18n default handler class and create alias `I18N` for the class
     *  @return this
     */
    protected function initI18N()
    {
        Log::log(null, Log::DEBUG);
        $class = Yz::$name . '\\I18N_Default';
        if (false === Yz::loadClass($class)) {
            $class = Yz::loadClass('Yz_I18N_Default');
        }
        class_alias($class, 'I18N');
        return $this;
    }

    /*************************
     *      Error handlers   *
     *************************/

    /** handler for php dumb warnings: ALWAYS log ALL warnings!
     *  @return void
     *  @throw Yz_Exception
     */
    public function errorHandler( $errno, $errstr, $errfile, $errline )
    {
        switch ( $errno ) {
            case E_WARNING:
            case E_USER_WARNING:
                $error = 'WARNING';
                break;
            case E_NOTICE:
            case E_USER_NOTICE:
                $error = 'NOTICE';
                break;
            case E_STRICT:
                $error = 'STRICT';
                break;
            case E_DEPRECATED: // since 5.3.0
            case E_USER_DEPRECATED:
                $error = 'DEPRECATED';
                break;
            case E_RECOVERABLE_ERROR: // since 5.2.0
                throw new Yz_Exception("PHP Recoverable error '{$errstr}' at {$errfile}@{$errline}");
            default:
                throw new Yz_Exception("Unrecognized PHP warning code: $errno");
        } // switch
        if (0 === error_reporting()) {
            $error = '@' . $error;
        }
        Log::log( "{$errfile}@{$errline}: PHP {$error}: {$errstr}", Log::ERROR );
        return;
    } // errorHandler()



    /**
     *  exceptionHandler() : Define default exception handler
     *  @return exit
     */
    public function exceptionHandler( Exception $ex )
    {
        if ( $ex instanceof Yz_Exception ) {
            $msg = $ex->toString();
        } else {
            $msg = sprintf( "Unhandled Exception '%s' in file '%s' at line '%d':\n%s",
                get_class( $ex ),
                $ex->getFile(),
                $ex->getLine(),
                $ex->getMessage()
            );
        }
        $exText = "$msg\nStack trace: " . $ex->getTraceAsString();

        Log::log( date('r'), Log::FATAL );
        Log::log( $exText, Log::FATAL );

        $this->displayErrorMessage( $exText );
        exit( 1 );
    } // exceptionHandler

    /**
     *  assertHandler() : Define default assert handler
     *  @return exit
     */
    public function assertHandler( $file, $line, $code )
    {
        Log::log( date('r'), Log::FATAL );
        $msg = "Assertion failed in {$file} at line {$line}: {$code}";
        Log::log( $msg, Log::FATAL );

        $this->displayErrorMessage( $msg );
        exit( 1 );
    } // exceptionHandler


} // class

